package com.blyat.xsoft.gateway.ribbon.loadbalancer.rule;

import com.blyat.xsoft.gateway.ribbon.loadbalancer.server.XSoftServer;
import com.google.common.collect.Lists;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * @Auther: syh
 * @Date: 2020/8/24
 * @Description:
 */
public class DynamicLoadBalanceRule extends AbstractLoadBalancerRule {
    private static final Logger logger = LoggerFactory.getLogger(DynamicLoadBalanceRule.class);

    private IClientConfig clientConfig;

    private LoadBalancerRuleService ruleService;
    private IRule rule;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        this.clientConfig = clientConfig;
    }

    @Override
    public Server choose(Object key) {
        rule.setLoadBalancer(getLoadBalancer());
        Server server;
        if (rule instanceof RandomRule) {
            List<Server> servers = this.getLoadBalancer().getReachableServers();
            List<InstanceWithWeight> instanceList = servers.stream().map(s -> {
                XSoftServer xSoftServer = (XSoftServer)s;
                return new InstanceWithWeight(s, xSoftServer.getWeight());
            }).collect(Collectors.toList());
            server = weightRandom(instanceList);
        } else {
            server = rule.choose(key);
        }
        return server;
    }

    public void setRuleService(LoadBalancerRuleService ruleService) {
        this.ruleService = ruleService;

        String clientName = clientConfig.getClientName();
        if (ruleService != null) {
            int ruleId = ruleService.getByClient(clientName);
            // todo 缓存rule
            Rule rule = Rule.of(ruleId);
            switch (rule) {
                case random:
                    this.rule = new RandomRule();
                    break;
                default:
                    this.rule = new RoundRobinRule();
            }
        }

        // 默认轮询算法
        if (rule == null) {
            rule = new RoundRobinRule();
        }
    }

    enum Rule {
        // todo 权重路由需要结合route的predicateList配置。后续优化。
        round(1), random(2);
        private int index;

        Rule(int index) {
            this.index = index;
        }

        public static Rule of(int index) {
            for (Rule value : Rule.values()) {
                if (value.index == index) {
                    return value;
                }
            }
            return round;
        }
    }

    private class InstanceWithWeight {
        private Server server;
        private Integer weight;

        public InstanceWithWeight(Server server, Integer weight) {
            this.server = server;
            this.weight = weight;
        }

        public Server getServer() {
            return server;
        }

        public Integer getWeight() {
            return weight;
        }
    }

    private Server weightRandom(List<InstanceWithWeight> list) {
        List<Server> instances = Lists.newArrayList();
        for (InstanceWithWeight instanceWithWeight : list) {
            int weight = instanceWithWeight.getWeight();
            for (int i = 0; i <= weight; i++) {
                instances.add(instanceWithWeight.getServer());
            }
        }
        int i = new Random().nextInt(instances.size());
        return instances.get(i);
    }
}
